#ifndef B2D_GLSHADER_H
#define B2D_GLSHADER_H

#include "ShaderProgram.h"
#include "ResourceFactory.h"
#include "GL/OpenGL.h"
#include "hashmap.h"

NS_B2D_BEGIN

///
/// OpenGL shader
///
class GLShader
	: public Shader
{
private:
	/// GL handle (name)
	GLuint mHandle;

public:
	GLShader(ShaderType type);
	virtual ~GLShader(void);

	void* getHandle(void) const { return reinterpret_cast<void*>(mHandle); }
	bool isInitialised(void) const { return mHandle != 0; }

	/// Get GLenum value for this type of shader
	static GLenum getGLType(ShaderType type);

private:
	void compileImpl(void);

	/// Create a shader
	void create(void);
	/// Destroy a shader
	void destroy(void);
};


///
/// Shader program
///
class GLShaderProgram final
	: public ShaderProgram
{
	B2D_OBJECT

private:
	enum { MAX_UNIFORM_NAME_LENGTH = 512 }; // Should be enough...

	typedef hashmap<string, GLint> UniformMap;

	GLuint mHandle;
	UniformMap mUniforms;

public:
	GLShaderProgram(void);
	~GLShaderProgram(void);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Get API specific handle.
	///
	/// \return	The handle
	////////////////////////////////////////////////////////////////////////////////////////////////////
	GLuint getHandle(void) const { return mHandle; }

	// ::IResource
	void load(const StreamPtr& stream);
	size_t getSizeEstimate(void) const;

	// ::IShaderProgram
	bool isInitialised(void) const { return mHandle != 0; }
	bool hasUniform(const string& name) const { return getUniformLocation(name) >= 0; }

	void setUniform1i(const string& name, int value);
	void setUniform1f(const string& name, float value);
	void setUniform2i(const string& name, int value0, int value1);
	void setUniform2f(const string& name, float value0, float value1);
	void setUniform3i(const string& name, int value0, int value1, int value2);
	void setUniform3f(const string& name, float value0, float value1, float value2);
	void setUniform4i(const string& name, int value0, int value1, int value2, int value3);
	void setUniform4f(const string& name, float value0, float value1, float value2, float value3);
	void setUniform1iv(const string& name, size_t count, const int* values);
	void setUniform1fv(const string& name, size_t count, const float* values);
	void setUniform2iv(const string& name, size_t count, const int* values);
	void setUniform2fv(const string& name, size_t count, const float* values);
	void setUniform3iv(const string& name, size_t count, const int* values);
	void setUniform3fv(const string& name, size_t count, const float* values);
	void setUniform4iv(const string& name, size_t count, const int* values);
	void setUniform4fv(const string& name, size_t count, const float* values);
	void setUniformMat2fv(const string& name, size_t count, bool transpose, const float* values);
	void setUniformMat3fv(const string& name, size_t count, bool transpose, const float* values);
	void setUniformMat4fv(const string& name, size_t count, bool transpose, const float* values);
	void setUniformSampler(const string& name, const ITexture& sampler);

private:
	void linkImpl(void);
	Shader* addShaderImpl(ShaderType type);
	void removeShaderImpl(Shader* shader);

	/// Retrive uniforms after linking
	void retrieveUniforms(void);
	/// Get the location of a uniform (or -1 if not present)
	GLint getUniformLocation(const string& name) const;
};

typedef DefaultResourceFactory<IShaderProgram, GLShaderProgram> GLShaderProgramFactory;


NS_B2D_END

#endif